local Msg = require("core.Msg")
local Timer = require("core.Timer")
local DB = require("common.DB")
local CommonDB = require("common.CommonDB")
local Log = require("common.Log")
local LuaMongo = _G.lua_mongo
local Util = require("common.Util")
local Grid = require("bag.Grid")
local OpenAct = require("present.OpenAct")
local RoleDBLogic = require("role.RoleDBLogic")
local YunYingLogic = require("yunying.YunYingLogic")
local MailManager = require("mail.MailManager")
local MailExcel = require("excel.mail")
local OpenActExcel = require("excel.openAct")
--[[
	开服活动排行DB操作,封装了一些公共处理方法
    db字段只是缓存排名好的数据(只是内存数据)
    db = {
        [rankType] = {                  -- 排名类型
            rankType = xxx,             -- 排名类型
            uuid2rank = {               -- 根据UUID获取名次   
                [uuid] = rank,          -- 玩家uuid = 排行名次
                ...
            },        
            sendUuid2rank = {           -- 根据UUID获取发送名次
                [uuid] = rank,          -- 玩家uuid = 真实发送的排行名次
                ...
            },
            rank2data = {               -- 根据名次获取DATA
                [rank] = {              -- 排行名次 = 玩家排名数据
                   uuid = xxx,          -- 玩家uuid
                   rankType = xxx,      -- 排名类型
                   value = xxx,         -- 排名值
                   time = xxx,          -- 排名值更新时间
                   _id = xxx,           -- db记录中的uuid
                },      
                ...
            },
            sendRank2data = {           -- 根据名次获取发送DATA
                [rank] = {              -- 排行名次 = 真实发送玩家排名数据
                   uuid = xxx,          -- 玩家uuid
                   rankType = xxx,      -- 排名类型
                   value = xxx,         -- 排名值
                   time = xxx,          -- 排名值更新时间
                   _id = xxx,           -- db记录中的uuid
                },      
                ...
            }
        },
        ...
    }

	DB:
        db_openServer_billboard = {     -- 开服排行类活动
            [uuid] = {
                uuid = xxx,             -- 玩家uuid
                rankType= xxx,          -- 排名类型
                value = xxx,            -- 排行值
                time = xxx,             -- 排行值更新时间
            }
        }
	local:
        getOpenActConfig()              -- 得到指定排名类型的活动配置
        getMinRankValue()				-- 得到排行榜中最低上榜条件数
        getMaxRank()				    -- 得到活动最多排名名次
        getRankNeedValue()			    -- 得到排行榜中名次对应需要达到的条件数
        getRankInfo()					-- 得到指定排行榜指定名次的配置信息
        getBoard()                      -- 获取指定排名类型DB数据
        getRank()                       -- 获取指定排名类型、指定用户的排名
        getSendRank()                   -- 获取指定排名类型、指定用户的发送排名
        cmpRank()                       -- 排名算法
        updateRank()                    -- 更新排名
        updateData()                    -- 更新数据
        loadBoard()                     -- 加载排行榜
        resertBoard()                   -- 重置排行数据
        initBoard()                     -- 初始排行榜
        getDBData()                     -- 根据类型+uuid从db中取data
        updateDBData()                  -- 更新DB数据
        billboardSet()                  -- 设置排名数据
        getMailID()                     -- 获取发送奖励的邮件ID
        getRewardInfoByInfo()			-- 得到指定排行榜指定配置信息的奖励信息
        rewardSend()					-- 奖励发送
        wrapOpenServerRankRewardList    -- 包装档位奖励详情数据
	public:
		initAfterStart()			    -- 启动就初始化	
		getMaxSendRank()				-- 得到最大发送排行名次数据
		wrapOpenServerRankList()		-- 包装档位名次数据
		wrapOwnerData()					-- 包装自己名次数据
        rewardQuery()                   -- 发送奖励详情
		onValueAdd()					-- 排行值增加回调
        onValueSet()                    -- 排行值设置回调
		onRewardSend()					-- 奖励发送回调
--]]
RANK_TYPE_DAY_TOPUP = 1         		-- 开服充值每日排名类型
RANK_TYPE_TOTAL_TOPUP = 2   			-- 开服充值累计排名类型
RANK_TYPE_DAY_DRAW_CARD = 3 			-- 开服招募每日排名类型
RANK_TYPE_TOTAL_DRAW_CARD = 4			-- 开服招募累计排名类型
RANK_TYPE_TOTAL_TOWER = 5               -- 开服爬塔累计排名类型

MAX_SEND_RANK = 10						-- 最大发送排名数

db = db or {}                  			-- [rankType] = board
QueryForData = {rankType = 1, uuid = 1} -- 通过排名类型和玩家uuid查找DB数据
QueryByUuid = {_id = 1}                 -- 通过DB的uuid更新DB数据
QueryByRankType = {rankType = 1}        -- 通过排名类型查找DB数据

local function getOpenActConfig(rankType)
    if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_TOTAL_TOPUP then 
        return OpenActExcel.topupRank
    end

    if rankType == RANK_TYPE_DAY_DRAW_CARD or rankType == RANK_TYPE_TOTAL_DRAW_CARD then 
        return OpenActExcel.drawCardRank
    end

    if rankType == RANK_TYPE_TOTAL_TOWER then
        return OpenActExcel.towerRank
    end
end

local MIN_RANK_VALUES
local function getMinRankValue(rankType)
	MIN_RANK_VALUES = MIN_RANK_VALUES or {}
	if MIN_RANK_VALUES[rankType] then return MIN_RANK_VALUES[rankType] end

    local openActConfig = getOpenActConfig(rankType)
    if not openActConfig then return end

    local minRankValue
    for _,info in ipairs(openActConfig) do
        if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_DAY_DRAW_CARD then
			if not minRankValue or minRankValue > info.dayNeedValue then 
				minRankValue = info.dayNeedValue
			end
        elseif rankType == RANK_TYPE_TOTAL_TOPUP or rankType == RANK_TYPE_TOTAL_DRAW_CARD or 
            rankType == RANK_TYPE_TOTAL_TOWER then
			if not minRankValue or minRankValue > info.totalNeedValue then 
				minRankValue = info.totalNeedValue
			end
        end
    end

	MIN_RANK_VALUES[rankType] = minRankValue
	return MIN_RANK_VALUES[rankType]
end

--local MAX_RANKS
local function getMaxRank(rankType)
    return 200
--[[
	MAX_RANKS = MAX_RANKS or {}
	if MAX_RANKS[rankType] then return MAX_RANKS[rankType] end

    local openActConfig = getOpenActConfig(rankType)
    if not openActConfig then return end

    local maxRank
    for _,info in ipairs(openActConfig) do
		if not maxRank or maxRank <= info.ranks[2] then 
			maxRank = info.ranks[2]
		end
    end
 
	MAX_RANKS[rankType] = maxRank
	return maxRank
    --]]
end

local RANK_NEED_VALUES
local function getRankNeedValue(rankType, rank)
	RANK_NEED_VALUES = RANK_NEED_VALUES or {}

	local minRankValue = getMinRankValue(rankType)
	if RANK_NEED_VALUES[rankType] then 
		return RANK_NEED_VALUES[rankType][rank] or minRankValue
	end

    local openActConfig = getOpenActConfig(rankType)
    if not openActConfig then return end

	RANK_NEED_VALUES[rankType] = {}

    for _,info in ipairs(openActConfig) do
        if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_DAY_DRAW_CARD then
			if minRankValue < info.dayNeedValue then
				for i = info.ranks[1],info.ranks[2] do
				 	RANK_NEED_VALUES[rankType][i] = info.dayNeedValue
				end
			else
				break
			end
        elseif rankType == RANK_TYPE_TOTAL_TOPUP or rankType == RANK_TYPE_TOTAL_DRAW_CARD or 
            rankType == RANK_TYPE_TOTAL_TOWER then
			if minRankValue < info.totalNeedValue then
				for i = info.ranks[1],info.ranks[2] do
				 	RANK_NEED_VALUES[rankType][i] = info.totalNeedValue
				end
			else
				break
			end
        end
    end
 
	return RANK_NEED_VALUES[rankType][rank] or minRankValue
end

local function getRankInfo(rankType, rank)
    local openActConfig = getOpenActConfig(rankType)
    if not openActConfig then return end

    local rankInfo
    for _,info in ipairs(openActConfig) do
		if rank >= info.ranks[1] and rank <= info.ranks[2] then
			rankInfo = info
			break
		end
    end
 
	return rankInfo
end

local function getRewardInfoByRank(rankType, rank)
    local openActConfig = getOpenActConfig(rankType)
    if not openActConfig then return end

	local rewardInfo
    for _,info in ipairs(openActConfig) do
        if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_DAY_DRAW_CARD then
			if rank >= info.ranks[1] and rank <= info.ranks[2] then
				rewardInfo = info.dayRewards
				break
			end
        elseif rankType == RANK_TYPE_TOTAL_TOPUP or rankType == RANK_TYPE_TOTAL_DRAW_CARD or 
            rankType == RANK_TYPE_TOTAL_TOWER then
			if rank >= info.ranks[1] and rank <= info.ranks[2] then
				rewardInfo = info.totalRewards
				break
			end
        end
    end

	return rewardInfo
end

local function getBoard(rankType)
    return db[rankType]
end

local function getRank(rankType, uuid)
    local board = getBoard(rankType)
    if not board then return end

    return board.uuid2rank[uuid]
end

local function getSendRank(rankType, uuid)
    local board = getBoard(rankType)
    if not board then return end

    return board.sendUuid2rank[uuid]
end

local function cmpRank(rankType, data1, data2)
    if data1.value ~= data2.value then
        return data1.value > data2.value
    end

    return data1.time < data2.time
end

local function updateRank(board)
    if not board then return end

    -- 清空普通排名和发送排名玩家数据
    for uuid in pairs(board.uuid2rank) do
        board.uuid2rank[uuid] = nil
        board.sendUuid2rank[uuid] = nil
    end

    -- 设置普通排名
    for rank, data in ipairs(board.rank2data) do
		board.uuid2rank[data.uuid] = rank
	end

    -- 清空发送排名
    for rank in ipairs(board.sendRank2data) do
        board.sendRank2data[rank] = nil
    end

    local minRankValue = getMinRankValue(board.rankType)                -- 排名最小需要的排名值
    local aleadyRankCnt = 0                                             -- 已经排好的玩家数
    local maxRank = getMaxRank(board.rankType)                          -- 最多排名玩家数量
    for rank = 1, maxRank do
        local rankNeedValue = getRankNeedValue(board.rankType, rank)    -- 取当前排名需要的排名值

        local rank2data = board.rank2data[aleadyRankCnt + 1]            -- 当前排的玩家
        if rank2data then                                               -- 还有玩家需要排
            if rank2data.value >= rankNeedValue then                    -- 达到排名值,可以排名
    		    board.sendUuid2rank[rank2data.uuid] = rank              -- 设置玩家发送排名
    		    board.sendRank2data[rank] = rank2data                   -- 设置发送排名数据
    		    aleadyRankCnt = aleadyRankCnt + 1                       -- 排名玩家数量+1
            elseif rankNeedValue <= minRankValue then                   -- 未达到排名值 且 当前排名值 小于等于 最小需要的排名值了,则后面的玩家都不需要排了
                    break
             else                                                       -- 未到达最低排名值,当前名次未有玩家达到
                board.sendRank2data[rank] = -1
            end
        else                                                            -- 没玩家排了直接停止
            break
        end
    end
end

local function updateData(rankType, data)
    local board = getBoard(rankType)
    if not board then return end

    local uuid = data.uuid
    local oldRank = getRank(rankType, uuid) or (#board.rank2data + 1)
    board.rank2data[oldRank] = data

    -- 向后比较(data.value 减少时),往排名低方向->
    for i = oldRank + 1, #board.rank2data do
        local data1 = board.rank2data[i - 1]
        local data2 = board.rank2data[i]
        if cmpRank(rankType, data1, data2) then
            break
        end
        board.rank2data[i - 1] = data2
        board.rank2data[i] = data1
    end

    -- 向前比较(data.value 增加时),往排名高方向<-
    for i = oldRank - 1, 1, -1 do
        local data1 = board.rank2data[i]
        local data2 = board.rank2data[i + 1]
        if cmpRank(rankType, data1, data2) then
            break
        end
        board.rank2data[i] = data2
        board.rank2data[i + 1] = data1
    end  

    local maxRank = getMaxRank(rankType)
    board.rank2data[maxRank + 1] = nil  
    return true
end

local function loadBoard()
    local cnt = 0
    LuaMongo.find(DB.db_openServer_billboard, nil)
    while true do
        local data = {}
        if not LuaMongo.next(data) then
            break
        end

        cnt = cnt + 1
        if cnt % 1000 == 0 then
            _G.collectgarbage("step", 100000)
        end
        updateData(data.rankType, data)
    end

    for _, board in pairs(db) do
        updateRank(board)
    end
end

local function resertBoard(board)
    board.uuid2rank = {}        -- 根据UUID获取名次   
    board.sendUuid2rank = {}    -- 根据UUID获取发送名次   
    board.rank2data = {}        -- 根据名次获取DATA
    board.sendRank2data = {}    -- 根据名次获取发送DATA
end

local function initBoard(rankType)
    local board = {}
    resertBoard(board)
    board.rankType = rankType
    db[rankType] = board
end

local function getDBData(rankType, uuid)
    QueryForData.rankType = rankType
    QueryForData.uuid = uuid

    local data = {}
    LuaMongo.find(DB.db_openServer_billboard, QueryForData)
    return LuaMongo.next(data) and data
end

local function updateDBData(data)
    local oldData = getDBData(data.rankType, data.uuid)
    if oldData then
        QueryByUuid._id = oldData._id
        LuaMongo.update(DB.db_openServer_billboard, QueryByUuid, data)
    else
        LuaMongo.insert(DB.db_openServer_billboard, data)
    end
end

local function billboardSet(data)
    local board = getBoard(data.rankType)
    if not board then return end

    updateData(data.rankType, data)
    updateRank(board)
    updateDBData(data)
end

local function getMailID(rankType, rankInfo)
	if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_DAY_DRAW_CARD then
		 return rankInfo.dayMailID
    elseif rankType == RANK_TYPE_TOTAL_TOPUP or rankType == RANK_TYPE_TOTAL_DRAW_CARD or 
        rankType == RANK_TYPE_TOTAL_TOWER then
        return rankInfo.totalMailID
    end
end

local function getRewardInfoByInfo(rankType, rankInfo)
	if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_DAY_DRAW_CARD then
		 return rankInfo.dayRewards
    elseif rankType == RANK_TYPE_TOTAL_TOPUP or rankType == RANK_TYPE_TOTAL_DRAW_CARD or 
        rankType == RANK_TYPE_TOTAL_TOWER then
        return rankInfo.totalRewards
    end
end

MAIL_SEND_CACHE = MAIL_SEND_CACHE or nil
local function rewardSend(rankType)
	local board = getBoard(rankType)
    if not board then return end

    MAIL_SEND_CACHE = MAIL_SEND_CACHE or {}
    MAIL_SEND_CACHE[rankType] = {}  

    local mailID
    for rank, rankData in ipairs(board.sendRank2data) do
        if rankData and rankData ~= -1 then
            local rankInfo = getRankInfo(rankType, rank)
            if rankInfo then
                MAIL_SEND_CACHE[rankType][rank] = rankData.uuid
                mailID = getMailID(rankType, rankInfo)
            --[[
	        local mailID = getMailID(rankType, rankInfo)
		    local rewardInfo = getRewardInfoByInfo(rankType, rankInfo)
			        
		    local mailConfig = MailExcel.mail[mailID]
		    local title = mailConfig.title
		    local senderName = mailConfig.senderName
		    local content = mailConfig.content		
			        			
		    MailManager.add(MailManager.SYSTEM, rankData.uuid, 
						title, Util.format(content, rank), rewardInfo, senderName)   --]]
            end                      	
        end
    end

    Timer.addLater(10, boardReward, mailID, rankType)

    -- 把内存中的数据重置,并移除DB中数据
    resertBoard(board)
    QueryByRankType.rankType = rankType
    LuaMongo.remove(DB.db_openServer_billboard, QueryByRankType)
end

local function wrapOpenServerRankRewardList(rankType, net, info)
    net.minRank = info.ranks[1]
    net.maxRank = info.ranks[2]

    local rewardInfo
    if rankType == RANK_TYPE_DAY_TOPUP or rankType == RANK_TYPE_DAY_DRAW_CARD then
        net.rankNeedValue = info.dayNeedValue
        rewardInfo = info.dayRewards
    elseif rankType == RANK_TYPE_TOTAL_TOPUP or rankType == RANK_TYPE_TOTAL_DRAW_CARD or 
        rankType == RANK_TYPE_TOTAL_TOWER then
        net.rankNeedValue = info.totalNeedValue
        rewardInfo = info.totalRewards
    end

 	local len = 0
	for index,itemInfo in ipairs(rewardInfo) do
		len = len + 1
		Grid.makeItem(net.items[index], itemInfo[1], itemInfo[2])
	end
	net.items[0] = len
end

function initAfterStart()
    if _G.is_middle == true then return end

    initBoard(RANK_TYPE_DAY_TOPUP)
    initBoard(RANK_TYPE_TOTAL_TOPUP)
    initBoard(RANK_TYPE_DAY_DRAW_CARD)
	initBoard(RANK_TYPE_TOTAL_DRAW_CARD)
    initBoard(RANK_TYPE_TOTAL_TOWER)

    loadBoard()
end
 
function getMaxSendRank(rankType)
	return MAX_SEND_RANK
end

function wrapOpenServerRankList(rankType, net, rank)
	local board = getBoard(rankType)
	local sendRankData = board.sendRank2data[rank]

	net.rank = rank
	net.rankNeedValue = getRankNeedValue(rankType, rank)
	if sendRankData and sendRankData ~= -1 then
		local userDB = RoleDBLogic.getDb(sendRankData.uuid)
		net.uid = sendRankData.uuid
		net.name = userDB.name
		net.head = userDB.head
		net.rankValue = sendRankData.value
		net.headFrame = userDB.headFrame
	else
		net.uid = "-1"
		net.name = ""
		net.head = -1
		net.rankValue = 0
		net.headFrame = -1
	end
 	
 	local rewardInfo = getRewardInfoByRank(rankType, rank)
 	local len = 0
 	if rewardInfo then
		for index,itemInfo in ipairs(rewardInfo) do
			len = len + 1
			Grid.makeItem(net.items[index], itemInfo[1], itemInfo[2])
		end
 	end
	net.items[0] = len
end

function wrapOwnerData(rankType, net, uuid)
	local maxRank = getMaxRank(rankType)
	local sendRank = getSendRank(rankType, uuid)
	local board = getBoard(rankType)
	local rankData
    if sendRank then
        rankData = board.sendRank2data[sendRank]
    else
        local rank = getRank(rankType, uuid)
        rankData = rank and board.rank2data[rank]
    end

	net.rank = sendRank or -1
	net.rankValue = rankData and rankData.value or 0

 	local len = 0
 	if sendRank then
 		local rewardInfo = getRewardInfoByRank(rankType, sendRank)
		for index,itemInfo in ipairs(rewardInfo) do
			len = len + 1
			Grid.makeItem(net.items[index], itemInfo[1], itemInfo[2])
		end
 	end
	net.items[0] = len
end

function rewardQuery(human, rankType)
    local openActConfig = getOpenActConfig(rankType)
 
	local msgRet = Msg.gc.GC_OPEN_SERVER_RANK_REWARD_QUERY
	msgRet.type = rankType

    local len = 0
    for _,info in ipairs(openActConfig) do
        len = len + 1
        wrapOpenServerRankRewardList(rankType, msgRet.list[len], info)
    end
    msgRet.list[0] = len

    Msg.send(msgRet, human.fd)
end

function onValueAdd(human, rankType, value)
 	local dbData = getDBData(rankType, human.db._id) or {}
    local tValue = (dbData.value or 0) + value
    onValueSet(human, rankType, tValue)
end

function onValueSet(human, rankType, tValue)
 	local dbData = getDBData(rankType, human.db._id) or {}
	dbData.value = tValue
	dbData.time = os.time()
	dbData.rankType = rankType
	dbData.uuid = human.db._id
	billboardSet(dbData)
end

function onRewardSend(funcID)
	local funcConfig = YunYingLogic.getFuncConfig(funcID)
	if not funcConfig then return end

    local config = OpenActExcel.openAct[funcConfig.param]
    if not config then return end

   local openDay = CommonDB.getServerOpenDay()
   if not openDay then return end

   if openDay > (config.eDay + 1) then return end

 	if funcConfig.param == OpenAct.OPEN_ACT_TOPUP_RANK then
 		rewardSend(RANK_TYPE_DAY_TOPUP)
        if openDay == (config.eDay + 1) then
 			rewardSend(RANK_TYPE_TOTAL_TOPUP)
 		end
    elseif funcConfig.param == OpenAct.OPEN_ACT_DRAW_CARD_RANK then
 		rewardSend(RANK_TYPE_DAY_DRAW_CARD)
 		if openDay == (config.eDay + 1) then
 			rewardSend(RANK_TYPE_TOTAL_DRAW_CARD)
 		end
    elseif funcConfig.param == OpenAct.OPEN_ACT_TOWER_RANK then
 		if openDay == (config.eDay + 1) then
 			rewardSend(RANK_TYPE_TOTAL_TOWER)
 		end    
	end
end

-- 定时器 邮件分开发，预防崩溃
function boardReward(mailID, rankType)
    if mailID and MAIL_SEND_CACHE and MAIL_SEND_CACHE[rankType] then
        local mailConfig = MailExcel.mail[mailID]   
	    local title = mailConfig.title
	    local senderName = mailConfig.senderName
	    local content = mailConfig.content	    
        local mailSends = MAIL_SEND_CACHE[rankType] 

        for rank,uuid in pairs(mailSends) do
            local rankInfo = getRankInfo(rankType, rank)
            if rankInfo then
                local rewardInfo = getRewardInfoByInfo(rankType, rankInfo)
			    if rewardInfo then	
		            MailManager.add(MailManager.SYSTEM, uuid, 
						        title, Util.format(content, rank), rewardInfo, senderName)           
                end        
            end
        end

        MAIL_SEND_CACHE[rankType] = nil
    end

    --[[
    -- 发完奖励后,把内存中的数据重置,并移除DB中数据
    local board = getBoard(rankType)
    if board then 
        resertBoard(board)
        QueryByRankType.rankType = rankType
        LuaMongo.remove(DB.db_openServer_billboard, QueryByRankType)  
    end--]]
end

--[[
function gmSendReward()
    print("OpenServerRankDB gmSendReward start")

    local  rankType = RANK_TYPE_DAY_TOPUP

	local board = getBoard(rankType)
    if not board then return end

    MAIL_SEND_CACHE = MAIL_SEND_CACHE or {}
    MAIL_SEND_CACHE[rankType] = {}  

    local mailID
    for rank, rankData in ipairs(board.sendRank2data) do
        if rankData and rankData ~= -1 then
            local rankInfo = getRankInfo(rankType, rank)
            if rankInfo then
                MAIL_SEND_CACHE[rankType][rank] = rankData.uuid
                mailID = getMailID(rankType, rankInfo)
            end                      	
        end
    end

    Timer.addLater(10, boardReward, mailID, rankType)

    -- 日志
    Log.write(Log.LOGID_DEBUG, "OpenServerRank", rankType)

    print("OpenServerRankDB gmSendReward end")
end--]]